package com.liebaut.services;

import com.liebaut.util.ReflectUtil;
import com.liebaut.util.annotations.Filterable;
import com.liebaut.util.jackson.EnumModule;
import org.apache.commons.beanutils.*;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.util.*;

@Service
public class BeanService implements InitializingBean {
    private ConvertUtilsBean convertUtilsBean;

    @Override
    public void afterPropertiesSet() throws Exception {
        BeanUtilsBean.setInstance(new BeanUtilsBean(new EnumConvertUtilsBean(), new PropertyUtilsBean()));
        convertUtilsBean = BeanUtilsBean.getInstance().getConvertUtils();
    }

    public Object convertAttributeFromString(Class clazz, String path, String value) {
        Class fieldType = findFieldType(clazz, path);
        return convertUtilsBean.convert(value, fieldType);
    }

    public void setAttribute(Object bean, String path, Object value) {
        try {
            BeanUtils.setProperty(bean, path, value);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public List<Field> getFilterableProperties(Class clazz) {
        List<Field> toRet = new ArrayList<Field>();
        Map<String, Field> fields = ReflectUtil.getAllFields(clazz);
        for (Field field : fields.values()) {
            Filterable filterable = field.getAnnotation(Filterable.class);
            if (filterable != null) {
                toRet.add(field);
            }
        }
        return toRet;
    }

    private Class findFieldType(Class clazz, String name) {
        String prefix, suffix;
        int i = name.indexOf('.');
        if (i > 0) {
            prefix = name.substring(0, i);
            suffix = name.substring(i + 1);
        } else {
            prefix = name;
            suffix = null;
        }
        Field field = ReflectionUtils.findField(clazz, prefix);
        if (field != null)
            if (suffix == null)
                return field.getType();
            else
                return findFieldType(field.getType(), suffix);
        else
            throw new IllegalArgumentException("Unknown field: " + name);
    }

    private static class EnumConvertUtilsBean extends ConvertUtilsBean {
        private final Map<Class, EnumConverter> enumConverters = new HashMap<Class, EnumConverter>();

        public Converter lookup(Class clazz) {
            final Converter converter = super.lookup(clazz);
            // no specific converter for this class, so it's neither a String, (which has a default converter),
            // nor any known object that has a custom converter for it. It might be an enum !
            if (converter == null && clazz.isEnum()) {
                return getEnumConverter(clazz);
            } else {
                return converter;
            }
        }

        private Converter getEnumConverter(Class<Enum> clazz) {
            if (enumConverters.containsKey(clazz))
                return enumConverters.get(clazz);
            else {
                EnumConverter converter = new EnumConverter(clazz);
                enumConverters.put(clazz, converter);
                return converter;
            }
        }

        private class EnumConverter implements Converter {
            Map<String, Object> map = new HashMap<String, Object>();

            public EnumConverter(Class<Enum> clazz) {
                try {
                    for (Enum en : clazz.getEnumConstants()) {
                        String name = EnumModule.enumName(en);
                        map.put(name, en);
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }

            public Object convert(Class type, Object value) {
                if (map.containsKey(value))
                    return map.get(value);
                else
                    throw new IllegalArgumentException("Unknown enum value: " + value);
            }
        }
    }
}
